-- 
-- eseps2.vhd
--   PS/2 keyboard interface for ESE-MSX
--   Revision 1.00
-- 
-- Copyright (c) 2006 Kazuhiro Tsujikawa (ESE Artists' factory)
-- All rights reserved.
-- 
-- Redistribution and use of this source code or any derivative works, are 
-- permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice, 
--    this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright 
--    notice, this list of conditions and the following disclaimer in the 
--    documentation and/or other materials provided with the distribution.
-- 3. Redistributions may not be sold, nor may they be used in a commercial 
--    product or activity without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
-- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
-- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
-- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-- OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
-- WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
-- OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
-- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--
--------------------------------------------------------------------------------
-- Update note
--------------------------------------------------------------------------------
-- Oct 05 2006 - Removed 101/106 toggle switch. 
-- Sep 23 2006 - Fixed a problem where some key events are lost after 101/106 
--               keyboard type switching.
-- Sep 22 2006 - Added external default keyboard layout input
-- May 21 2005 - Modified to support Quartus2we5.
-- Jan 24 2004 - Fixed a locking key problem if 101/106 keyboard type is 
--               switched during pressing keys.
--             - Fixed a problem where a comma key is pressed after a 
--               pause key.
-- Jan 23 2004 - Added a 101 keyboard table.
--------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

entity eseps2 is
  port (
    clk21m   : in std_logic;
    reset    : in std_logic;
    clkena   : in std_logic;

    Kmap     : in std_logic;

    Caps     : inout std_logic;
    Kana     : inout std_logic;
    Paus     : inout std_logic;
    Scro     : inout std_logic;
    Reso     : inout std_logic;
    
    -- | b7  | b6   | b5   | b4   | b3  | b2  | b1  | b0  |
    -- | SHI | --   | PgUp | PgDn | F9  | F10 | F11 | F12 |
    Fkeys    : out std_logic_vector(7 downto 0);

    pPs2Clk  : inout std_logic;
    pPs2Dat  : inout std_logic;

    PpiPortC : in  std_logic_vector(7 downto 0);
    pKeyX    : out std_logic_vector(7 downto 0)
  );
end eseps2;

architecture RTL of eseps2 is

  signal KeyWe   : std_logic;
  signal KeyRow  : std_logic_vector(7 downto 0);
  signal iKeyCol : std_logic_vector(7 downto 0);
  signal oKeyCol : std_logic_vector(7 downto 0);
  signal MtxIdx  : std_logic_vector(10 downto 0);
  signal MtxPtr  : std_logic_vector(7 downto 0);

  component ram is
    port (
      adr : in  std_logic_vector(7 downto 0);
      clk : in  std_logic;
      we  : in  std_logic;
      dbo : in  std_logic_vector(7 downto 0);
      dbi : out std_logic_vector(7 downto 0)
    );
  end component;

  component keymap is
    port (
      adr : in std_logic_vector(10 downto 0);
      clk : in std_logic;
      dbi : out std_logic_vector(7 downto 0)
    );
  end component;

begin

  process(clk21m, reset, Kmap)

    type typPs2Seq is (Ps2Idle, Ps2Rxd, Ps2Txd, Ps2Stop);
    variable Ps2Seq : typPs2Seq;
    variable Ps2Chg : std_logic;
    variable Ps2brk : std_logic;
    variable Ps2xE0 : std_logic;
    variable Ps2xE1 : std_logic;
    variable Ps2Cnt : std_logic_vector(3 downto 0);
    variable Ps2Clk : std_logic_vector(2 downto 0);
    variable Ps2Dat : std_logic_vector(7 downto 0);
    variable Ps2Led : std_logic_vector(8 downto 0);
    variable Ps2Skp : std_logic_vector(2 downto 0);
    variable timout : std_logic_vector(15 downto 0);

    variable Ps2Caps : std_logic;
    variable Ps2Kana : std_logic;
    variable Ps2Paus : std_logic;
    variable Ps2Scro : std_logic; -- used for 101/106 key table switching
    variable Ps2Shif : std_logic; -- real shift status 
    variable Ps2Vshi : std_logic; -- virtual shift status
    variable oFkeys  : std_logic_vector(7 downto 0);

    variable KeyId   : std_logic_vector(8 downto 0);

    type typMtxSeq is (MtxIdle, MtxSettle, MtxClean, MtxRead, MtxWrite, MtxEnd, MtxReset);
    variable MtxSeq : typMtxSeq;
    variable MtxTmp : std_logic_vector(3 downto 0);

  begin

    if (reset = '1') then

      Ps2Seq := Ps2Idle;
      Ps2Chg := '0';
      Ps2brk := '0';
      Ps2xE0 := '0';
      Ps2xE1 := '0';
      Ps2Cnt := (others => '0');
      Ps2Clk := (others => '1');
      Ps2Dat := (others => '1');
      timout := (others => '1');
      Ps2Led := (others => '1');
      Ps2Vshi:= '0';
      Ps2Skp := "000";

      Ps2Caps := '1';
      Ps2Kana := '1';
      Ps2Paus := '0';
      Paus    <= '0';
      Reso    <= '0';
      
      oFkeys  := (others=>'0');
      
      Scro    <= Kmap; -- default keyboard layout / '0': Japanese-106, '1': English-101

      MtxSeq := MtxIdle;

      pPs2Dat <= 'Z';
      pKeyX <= (others => '1');

      KeyWe   <= '0';
      KeyRow  <= (others => '0');
      iKeyCol <= (others => '0');

    elsif (clk21m'event and clk21m = '1') then

      if clkena = '1' then
        -- "Scan table > MSX key-matrix" conversion
        case MtxSeq is
          when MtxIdle =>
                   
            if Ps2Chg = '1' then 
 
              KeyId := Ps2xE0 & Ps2Dat;
              if Scro = '1' then
                MtxSeq := MtxSettle;
                MtxIdx <= "0" & (not Ps2Shif) & KeyId;
              else
                MtxSeq := MtxRead;
                MtxIdx <= "10" & KeyId;
              end if;
              pKeyX <= (others => '1');
        
            else
        
              for i in 7 downto 1 loop
                if oKeyCol(i) = '1' then
                  pKeyX(i) <= '0';
                else
                  pKeyX(i) <= '1';
                end if;
              end loop;
              if PpiPortC(3 downto 0) = "0110" then
                if (Scro = '0' and oKeyCol(0) = '1') or (Scro = '1' and Ps2Vshi = '1') then
                  pKeyX(0) <= '0';
                else
                  pKeyX(0) <= '1';
                end if;
              else
                if oKeyCol(0) = '1' then
                  pKeyX(0) <= '0';
                else
                  pKeyX(0) <= '1';
                end if;
              end if;
              KeyRow <= "0000" & PpiPortC(3 downto 0);
            end if;
          
          when MtxSettle =>
            MtxSeq := MtxClean;
            KeyWe  <= '0';
            KeyRow <= "0000" & MtxPtr(3 downto 0);
     
          when MtxClean =>                          
            MtxSeq := MtxRead;
            KeyWe <= '1';
            iKeyCol <= oKeyCol;
            iKeyCol(conv_integer(MtxPtr(6 downto 4))) <= '0';
            MtxIdx <= "0" & Ps2Shif & KeyId;
          
          when MtxRead =>
            MtxSeq := MtxWrite;
            KeyWe <= '0';
            KeyRow <= "0000" & MtxPtr(3 downto 0);
            if( Ps2Brk = '0' ) then 
              Ps2Vshi := MtxPtr(7);
            else
              Ps2Vshi := Ps2Shif;
            end if;

          when MtxWrite  =>
            MtxSeq := MtxEnd;
            KeyWe <= '1';
            iKeyCol <= oKeyCol;
            iKeyCol(conv_integer(MtxPtr(6 downto 4))) <= not Ps2brk;

          when MtxEnd  =>
            MtxSeq := MtxIdle;
            KeyWe <= '0';
            KeyRow <= "0000" & PpiPortC(3 downto 0);
            Ps2Chg := '0';
            Ps2brk := '0';
            Ps2xE0 := '0';
            Ps2xE1 := '0';

          when MtxReset =>
            if MtxTmp = "1011" then
              MtxSeq := MtxIdle;
              KeyWe <= '0';
              KeyRow <= "0000" & PpiPortC(3 downto 0);
            end if;
            KeyWe   <= '1';
            KeyRow  <= "0000" & MtxTmp;
            iKeyCol <= (others => '0');
            MtxTmp := MtxTmp + '1';

          when others =>
            MtxSeq := MtxIdle;

        end case;

      end if;

      -- "PS/2 interface > Scan table" conversion
      if (clkena = '1') then

        if (Ps2Clk = "100") then        -- clk inactive
          Ps2Clk(2) := '0';
          timout := X"01FF";            -- countdown timeout (143us = 279ns x 512clk, exceed 100us)

          if (Ps2Seq = Ps2Idle) then
            pPs2Dat <= 'Z';
            Ps2Seq := Ps2Rxd;
            Ps2Cnt := (others => '0');
          elsif (Ps2Seq = Ps2Txd) then
            if (Ps2Cnt = "1000") then
              Ps2Caps := Caps;
              Ps2Kana := Kana;
              Ps2Paus := Paus;
              Ps2Scro := Scro;
              Ps2Seq := Ps2Idle;
            end if;
            pPs2Dat <= Ps2Led(0);
            Ps2Led := Ps2Led(0) & Ps2Led(8 downto 1);
            Ps2Dat := '1' & Ps2Dat(7 downto 1);
            Ps2Cnt := Ps2Cnt + 1;
          elsif (Ps2Seq = Ps2Rxd) then
            if (Ps2Cnt = "0111") then
              Ps2Seq := Ps2Stop;
            end if;
            Ps2Dat := pPs2Dat & Ps2Dat(7 downto 1);
            Ps2Cnt := Ps2Cnt + 1;
            
          elsif (Ps2Seq = Ps2Stop) then
            Ps2Seq := Ps2Idle;
            if (Ps2Dat = X"AA") then    -- BAT code (basic assurance test)
              Ps2Caps := not Caps;
              Ps2Kana := not Kana;
              Ps2Paus := not Paus;
              Ps2Scro := not Scro;
            elsif Ps2Skp /= "000" then  -- Skip some sequences.
              Ps2Skp := Ps2Skp - 1;
            elsif (Ps2Dat = X"14" and Ps2xE0 = '0' and Ps2xE1 = '1') then -- pause/break make
              if Ps2brk ='0' then
                Paus <= not Paus;  -- CPU pause
                Ps2Skp := "110";   -- Skip the next 6 sequences.
              end if;
            elsif (Ps2Dat = X"7C" and Ps2xE0 = '1' and Ps2xE1 = '0') then -- printscreen make
              if Ps2brk = '0' then
                Reso <= not Reso;  -- toggle display mode
              end if;
              Ps2Chg := '1';
            elsif (Ps2Dat = X"7D" and Ps2xE0 = '1' and Ps2xE1 = '0') then -- PgUp make
              if Ps2brk = '0' then
                oFkeys(5) := not oFkeys(5);
              end if;
              Ps2Chg := '1';
            elsif (Ps2Dat = X"7A" and Ps2xE0 = '1' and Ps2xE1 = '0') then -- PgDn make
              if Ps2brk = '0' then
                oFkeys(4) := not oFkeys(4);
              end if;
              Ps2Chg := '1';
            elsif (Ps2Dat = X"01" and Ps2xE0 = '0' and Ps2xE1 = '0') then -- F9 make
              if Ps2brk = '0' then
                oFkeys(3) := not oFkeys(3);
              end if;
              Ps2Chg := '1';
            elsif (Ps2Dat = X"09" and Ps2xE0 = '0' and Ps2xE1 = '0') then -- F10 make
              if Ps2brk = '0' then
                oFkeys(2) := not oFkeys(2);
              end if;
              Ps2Chg := '1';
            elsif (Ps2Dat = X"78" and Ps2xE0 = '0' and Ps2xE1 = '0') then -- F11 make
              if Ps2brk = '0' then
                oFkeys(1) := not oFkeys(1);
              end if;
              Ps2Chg := '1';
            elsif (Ps2Dat = X"07" and Ps2xE0 = '0' and Ps2xE1 = '0') then -- F12 make
              if Ps2brk = '0' then
                oFkeys(0) := not oFkeys(0);  -- toggle OnScreenDisplay enable.
              end if;
              Ps2Chg := '1';
            -- elsif (Ps2Dat = X"7E" and Ps2xE0 = '0' and Ps2xE1 = '0') then -- scroll-lock make
            --  if Ps2brk ='0' then
            --    Scro <= not Scro;  -- toggle scroll lock (currently used for 101/106 keyboard switch)
            --    MtxTmp := "0000";
            --    MtxSeq := MtxReset;
            --  end if;
            --  Ps2Chg := '1';
            elsif ((Ps2Dat = X"12" or Ps2Dat = X"59") and Ps2xE0 = '0' and Ps2xE1 ='0' ) then -- shift make
              Ps2Shif:= not Ps2brk;
              oFkeys(7) := Ps2Shif;
              Ps2Chg := '1';
            elsif (Ps2Dat = X"F0") then -- break code
              Ps2brk := '1';
            elsif (Ps2Dat = X"E0") then -- extnd code E0
              Ps2xE0 := '1';
            elsif (Ps2Dat = X"E1") then -- extnd code E1 (ignore)
              Ps2xE1 := '1';
            elsif (Ps2Dat = X"FA") then  -- Ack of "EDh" command
              Ps2Seq := Ps2Idle;
            else
              Ps2Chg := '1';
            end if;

          end if;

        elsif (Ps2Clk = "011") then     -- clk active
          Ps2Clk(2) := '1';
          timout := X"01FF";            -- countdown timeout (143us = 279ns x 512clk, exceed 100us)

        elsif (timout = X"0000") then   -- timeout

          pPs2Dat <= 'Z';
          Ps2Seq := Ps2Idle;            -- to Idle state

          if (Ps2Seq = Ps2Idle and Ps2Clk(2) = '1') then

            if (Ps2Dat = X"FA" and Ps2Led = "111101101") then
              Ps2Seq := Ps2Txd;         -- Tx data state
              pPs2Dat <= '0';
--   d2 - Caps Lock; d1 - Num Lock; d0 - Scroll Lock
              --Ps2Led := (Caps xor Kana xor Paus xor '1') & "00000" & (not Caps) & (not Kana) & Paus;
              Ps2Led := (Caps xor Kana xor Paus xor '1') & "00000" & (not Caps) & Paus & (not Kana);
              timout := X"FFFF";        -- countdown timeout (18.3ms = 279ns x 65536clk, exceed 1ms)

            elsif (Caps /= Ps2Caps or Kana /= Ps2Kana or Paus /= Ps2Paus) then
              Ps2Seq := Ps2Txd;         -- Tx data state
              pPs2Dat <= '0';
              Ps2Led := "111101101";    -- Command EDh
              timout := X"FFFF";        -- countdown timeout (18.3ms = 279ns x 65536clk, exceed 1ms)
            end if;
          end if;
        else
          timout := timout - 1;         -- countdown timeout
        end if;
        Ps2Clk(1) := Ps2Clk(0);
        Ps2Clk(0) := pPs2Clk;                                
      end if;
    end if;
    Fkeys <= oFkeys;
  end process;
--
  pPs2Clk <= 'Z';
--
  U1 : ram port map(KeyRow, clk21m, KeyWe, iKeyCol, oKeyCol);
  U2 : keymap port map( MtxIdx, clk21m, MtxPtr );
--
end RTL;
